package com.zb.zeus.common.engine;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.BoundType;
import com.google.common.collect.Lists;
import com.google.common.collect.Range;
import com.zb.zeus.common.core.exception.BaseException;
import com.zb.zeus.common.engine.domain.CalcConst;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;

import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit;

/**
 * 区间相关API
 *
 * @author fujianjian
 * @since 2021/4/14 10:26
 */
@Slf4j
public class Region {
    private static LoadingCache<String, RangeEntry<BigDecimal>> REGION_CACHE = CacheBuilder.newBuilder()
            .maximumSize(1000L).expireAfterAccess(1L, TimeUnit.DAYS)
            .build(new CacheLoader<String, RangeEntry<BigDecimal>>() {
                @Override
                public RangeEntry<BigDecimal> load(String express) throws Exception {
                    //return Region.convertSingleRange(express);
                    return Region.convertRangeEntry(express);
                }
            });
    private static LoadingCache<String, List<String>> EXPRESS_CACHE = CacheBuilder.newBuilder()
            .maximumSize(100L).expireAfterAccess(1L, TimeUnit.DAYS)
            .build(new CacheLoader<String, List<String>>() {
                @Override
                public List<String> load(String express) throws Exception {
                    return Region.parseSemiExpress(express);
                    //return express.indexOf(CommonConst.SEMI) > 0 ?
                    //        Region.parseSemiExpress(express) : Region.parseExpress(express);
                }
            });
    private final static String DELIM = ";,=()[]";

    private final static String ERR_DESC = "区间表达式书写错误";

    public static List<RangeEntry<BigDecimal>> getRangeEntry(String express) {
        try {
            List<String> childList = EXPRESS_CACHE.get(express);
            List<RangeEntry<BigDecimal>> entries = Lists.newArrayListWithExpectedSize(childList.size());
            for (String child : childList) {
                RangeEntry<BigDecimal> entry = REGION_CACHE.get(child);
                entries.add(entry);
            }
            Collections.sort(entries);
            return entries;
        } catch (Exception e) {
            throw new BaseException(ERR_DESC, e);
        }
    }


    protected static List<String> parseSemiExpress(String express) {
        String tmpExpress = express.replace(StringUtils.SPACE, StringUtils.EMPTY);
        List<String> targetList = Lists.newLinkedList();
        StringTokenizer tokenizer = new StringTokenizer(tmpExpress, CalcConst.SEMI);
        while (tokenizer.hasMoreTokens()) {
            String s = tokenizer.nextToken().trim();
            if (StringUtils.isNotEmpty(s)) {
                targetList.add(s);
            }
        }
        return targetList;
    }

    protected static List<String> parseExpress(String express) {
        String tmpExpress = express.replace(StringUtils.SPACE, StringUtils.EMPTY);
        List<String> targetList = Lists.newLinkedList();
        StringTokenizer tokenizer = new StringTokenizer(tmpExpress, "=,()[]", true);
        String childExpress = StringUtils.EMPTY;
        while (tokenizer.hasMoreTokens()) {
            String s = tokenizer.nextToken().trim();
            childExpress += s;
            if (s.equals(")") || s.equals("]")) {
                targetList.add(childExpress);
                childExpress = StringUtils.EMPTY;
                if (tokenizer.hasMoreTokens()) {
                    tokenizer.nextToken();
                }
            }
        }
        return targetList;
    }

    /**
     * 解析区间维度表达式
     * eg：
     * 表达式格式
     * 一维: 1=(1,2)
     * 二维: 1=(1,2),(2,23]
     * 三维: 1=(1,2),(2,23],(23,)
     * ......
     *
     * @param express 表达式
     * @return
     */
    protected static RangeEntry<BigDecimal> convertRangeEntry(String express) {
        StringTokenizer tokenizer = new StringTokenizer(express, DELIM, true);
        String lows = null, ups = null;
        BoundType lowType = null;
        List<Range<BigDecimal>> rangeList = Lists.newLinkedList();
        BigDecimal targetVal = null;
        while (tokenizer.hasMoreTokens()) {
            String tk = tokenizer.nextToken();

            if (StringUtils.equals(tk, CalcConst.LEFT_BRACKETS)) {
                // 括号没有闭合
                if (Objects.nonNull(lowType)) {
                    throw new BaseException(ERR_DESC);
                }
                lowType = BoundType.OPEN;
                continue;
            }
            if (StringUtils.equals(tk, CalcConst.RIGHT_BRACKETS)) {
                rangeList.add(buildRange(lows, lowType, ups, BoundType.OPEN));
                lows = ups = null;
                lowType = null;
                if (tokenizer.hasMoreTokens()) {
                    tokenizer.nextToken();
                }
                continue;
            }
            if (StringUtils.equals(tk, CalcConst.LEFT_SQUARE_BRACKETS)) {
                // 括号没有闭合
                if (Objects.nonNull(lowType)) {
                    throw new BaseException(ERR_DESC);
                }
                lowType = BoundType.CLOSED;
                continue;
            }
            if (StringUtils.equals(tk, CalcConst.RIGHT_SQUARE_BRACKETS)) {
                rangeList.add(buildRange(lows, lowType, ups, BoundType.CLOSED));
                lows = ups = null;
                lowType = null;
                if (tokenizer.hasMoreTokens()) {
                    tokenizer.nextToken();
                }
                continue;
            }
            if (StringUtils.equals(tk, CalcConst.COMMA)) {
                // ,多写了
                if (Objects.nonNull(lows)) {
                    throw new BaseException(ERR_DESC);
                }
                lows = ups;
                ups = null;
                continue;
            }
            if (StringUtils.equals(tk, CalcConst.EQUAL_SIGN)) {

                if (Objects.isNull(targetVal)) {
                    targetVal = NumberUtils.createBigDecimal(ups);
                    ups = null;
                    continue;
                } else if (StringUtils.isNotEmpty(ups)) {
                    //  等于号写多了
                    throw new BaseException(ERR_DESC);
                }

            }
            ups = tk;
        }
        // 括号没有闭合
        if (Objects.nonNull(lowType)) {
            throw new BaseException(ERR_DESC);
        }
        RangeEntry<BigDecimal> entry;
        if (rangeList.size() == 1) {
            SingleRangeEntry<BigDecimal> singleRangeEntry = new SingleRangeEntry<>();
            singleRangeEntry.val = targetVal;
            singleRangeEntry.step = StringUtils.isEmpty(ups) ? BigDecimal.ZERO : NumberUtils.createBigDecimal(ups);
            singleRangeEntry.range = rangeList.get(0);
            entry = singleRangeEntry;
        } else {
            MultiRangeEntry<BigDecimal> multiRangeEntry = new MultiRangeEntry<>();
            multiRangeEntry.val = targetVal;
            multiRangeEntry.step = StringUtils.isEmpty(ups) ? BigDecimal.ZERO : NumberUtils.createBigDecimal(ups);
            multiRangeEntry.rangeList = rangeList;
            entry = multiRangeEntry;
        }
        return entry;
    }

    protected static Range<BigDecimal> buildRange(String lows, BoundType lowType, String ups, BoundType upType) {
        Range<BigDecimal> targetRange;
        try {
            if (StringUtils.isEmpty(lows) && StringUtils.isNotEmpty(ups)) {
                targetRange = Range.upTo(NumberUtils.createBigDecimal(ups), upType);
            } else if (StringUtils.isNotEmpty(lows) && StringUtils.isEmpty(ups)) {
                targetRange = Range.downTo(NumberUtils.createBigDecimal(lows), lowType);
            } else if (StringUtils.isNotEmpty(lows) && StringUtils.isNotEmpty(ups)) {
                targetRange = Range.range(NumberUtils.createBigDecimal(lows), lowType,
                        NumberUtils.createBigDecimal(ups), upType);
            } else {
                throw new BaseException(ERR_DESC);
            }
            return targetRange;
        } catch (BaseException be) {
            throw be;
        } catch (Exception e) {
            throw new BaseException(ERR_DESC, e);
        }

    }

    /**
     * 解析一维区间维度表达式
     * eg:
     *  表达式格式
     *   一维: 1=(1,2)
     * @param express 表达式
     * @return
     */
    protected static RangeEntry<BigDecimal> convertSingleRange(String express) {
        String[] expressParts = express.split("(\\=|\\,)");
        SingleRangeEntry<BigDecimal> entry = new SingleRangeEntry<>();
        String lows, ups;
        if (expressParts.length == 4) {
            entry.val = NumberUtils.createBigDecimal(expressParts[0]);
            entry.step = NumberUtils.createBigDecimal(expressParts[3]);
            lows = expressParts[1];
            ups = expressParts[2];
        } else if (expressParts.length == 3) {
            entry.val = NumberUtils.createBigDecimal(expressParts[0]);
            entry.step = BigDecimal.ZERO;
            lows = expressParts[1];
            ups = expressParts[2];
        }else if (expressParts.length == 2) {
            entry.val = BigDecimal.ZERO;
            entry.step = BigDecimal.ZERO;
            lows = expressParts[0];
            ups = expressParts[1];
        } else {
            throw new BaseException(ERR_DESC);
        }
        BigDecimal lower, upper;
        BoundType lowType, upType;
        if (lows.contains(CalcConst.LEFT_BRACKETS)) {
            if (StringUtils.isEmpty(lows = lows.replace(CalcConst.LEFT_BRACKETS, StringUtils.EMPTY))) {
                lower = null;
            } else {
                lower = NumberUtils.createBigDecimal(lows);
            }
            lowType = BoundType.OPEN;
        } else if (lows.contains(CalcConst.LEFT_SQUARE_BRACKETS)) {
            if (StringUtils.isEmpty(lows = lows.replace(CalcConst.LEFT_SQUARE_BRACKETS, StringUtils.EMPTY))) {
                lower = null;
            } else {
                lower = NumberUtils.createBigDecimal(lows);
            }
            lowType = BoundType.CLOSED;
        } else {
            throw new BaseException(ERR_DESC);
        }

        if (ups.contains(CalcConst.RIGHT_BRACKETS)) {
            if (StringUtils.isEmpty(ups = ups.replace(CalcConst.RIGHT_BRACKETS, StringUtils.EMPTY))) {
                upper = null;
            } else {
                upper = NumberUtils.createBigDecimal(ups);
            }
            upType = BoundType.OPEN;
        } else if (ups.contains(CalcConst.RIGHT_SQUARE_BRACKETS)) {
            if (StringUtils.isEmpty(ups = ups.replace(CalcConst.LEFT_SQUARE_BRACKETS, StringUtils.EMPTY))) {
                upper = null;
            } else {
                upper = NumberUtils.createBigDecimal(ups);
            }
            upType = BoundType.CLOSED;
        } else {
            throw new BaseException(ERR_DESC);
        }
        if (Objects.isNull(lower) && Objects.nonNull(upper)) {
            entry.range = Range.upTo(upper, upType);
        } else if (Objects.nonNull(lower) && Objects.isNull(upper)) {
            entry.range = Range.downTo(lower, lowType);
        } else if (Objects.nonNull(lower) && Objects.nonNull(upper)) {
            entry.range = Range.range(lower, lowType, upper, upType);
        } else {
            throw new BaseException(ERR_DESC);
        }
        return entry;
    }

    @Data
    protected static class SingleRangeEntry<T extends Comparable> implements RangeEntry<T> {
        Range<T> range;
        T val;
        T step;

        @Override
        public Range<T> getIndexRange(int index) {
            return range;
        }
    }

    @Data
    protected static class MultiRangeEntry<T extends Comparable> implements RangeEntry<T> {
        List<Range<T>> rangeList;
        T val;
        T step;

        @Override
        public Range<T> getIndexRange(int index) {
            return rangeList.get(index);
        }
    }

    public interface RangeEntry<T extends Comparable> extends Comparable<RangeEntry<T>> {

        default Range<T> getIndexRange(int index) {
            throw new BaseException("方法暂未实现");
        }

        default T getVal() {
            throw new BaseException("方法暂未实现");
        }

        default T getStep() {
            throw new BaseException("方法暂未实现");
        }
        @Override
        default int compareTo(RangeEntry<T> o) {
            return getVal().compareTo(o.getVal());
        }
    }


}

